//
// C++ Interface: BasicViewer
//
// Description: widget class to display a scene of geometric objects, using OpenGL, implementation
//
// Author: Burkhard Lehner <lehner@informatik.uni-kl.de>, (C) 2006

#include <math.h> // for sqrt

#include "glbox.h"
//#include "renderobject.h"
//Added by qt3to4:
#include <QWheelEvent>
#include <QMouseEvent>

GLBox::GLBox( QWidget* parent, const QGLWidget* shareWidget )
	: QGLWidget( parent,  shareWidget )
{
    // start in moving mode
    setMovingMode();

    initPoints1();
    initPoints2();
    initPoints3();
    initPoints4();

    m_coefficients = lagrangeInterpolation(m_points1);
    m_polynomial = evaluateLagrange(m_coefficients, -9.0, 9.0, 100);

    //for(int i=-100;i<100;i++)
    //    m_polynomial.push_back(Vector(i/10.0,(i/10.0)*(i/10.0)));
}

GLBox::~GLBox()
{
    /*
    // delete all objects of the scene
    for (unsigned int i = 0; i < objects.size(); ++i)
        delete objects [i];
    // clear the list
    objects.clear();
    */
}

/*
void GLBox::addRenderObject(RenderObject *o)
{
    objects.push_back (o);
    updateGL();
}
*/

void GLBox::initializeGL()
{
    // this method is called exactly once on program start

    glEnable (GL_DEPTH_TEST);   // enable z-buffer test
    glDepthFunc (GL_LESS);

    glShadeModel (GL_FLAT);   // use flat shading
    glLightModeli (GL_LIGHT_MODEL_TWO_SIDE, 1);

    // initialize the matrices before defining the light sources
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    // initialize ViewMatrix as the identity matrix
    glGetDoublev (GL_MODELVIEW_MATRIX, ViewMatrix);
    scale = 1.0;

    // Lighting
    GLfloat lpos[4] = { 100.0, 100.0, 0.0,  1.0}; // coordinates light source 1
    GLfloat lpos2[4] = { -100.0, 100.0, 0.0,  1.0}; // coordinates light source 2
    GLfloat ambi[4] = {  0.1,  0.1,  0.1,  1.0}; // RGBA ambient
    GLfloat diff[4] = {  1.0,  1.0,  1.0,  1.0}; // RGBA diffuse
    GLfloat spec[4] = {  0.5,  0.5,  0.5,  1.0}; // RGBA specular

    // set light source 1
    glLightfv (GL_LIGHT1, GL_POSITION, lpos);
    glLightfv (GL_LIGHT1, GL_AMBIENT,  ambi);
    glLightfv (GL_LIGHT1, GL_DIFFUSE,  diff);
    glLightfv (GL_LIGHT1, GL_SPECULAR, spec);
    glEnable (GL_LIGHT1);

    // set light source 2
    glLightfv (GL_LIGHT2, GL_POSITION, lpos2);
    glLightfv (GL_LIGHT2, GL_AMBIENT,  ambi);
    glLightfv (GL_LIGHT2, GL_DIFFUSE,  diff);
    glLightfv (GL_LIGHT2, GL_SPECULAR, spec);
    glEnable (GL_LIGHT2);

    // enable light sources
    glEnable (GL_LIGHTING);

    // define background color (RGBA, here: light blue)
    glClearColor ( .5, .5, 1.0, 1.0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void GLBox::resizeGL( int, int )
{
    // this is called when showing the widget for the first time, and every time the size of the widget changes
    // since the widget size is queried when rendering, there is nothing we have to do here
	
}

void GLBox::initPoints1()
{
    // If list is not empty, delete all points.
    if (!m_points1.empty())
        m_points1.clear();

    //parabola
    m_points1.push_back(Vector( -4.0, 8.0));
    m_points1.push_back(Vector( -1.0, -7.0));
    m_points1.push_back(Vector( 0.0, -8.0));
    m_points1.push_back(Vector( 2.0, -4.0));
    m_points1.push_back(Vector( 3.0, 1.0));
}

void GLBox::initPoints2()
{
    // If list is not empty, delete all points.
    if (!m_points2.empty())
        m_points2.clear();

    //gaussian curve
    m_points2.push_back((Vector(-5.0, 0.0)));
    m_points2.push_back((Vector(-4.0, 0.05)));
    m_points2.push_back((Vector(-3.0, 0.075)));
    m_points2.push_back((Vector(-2.0, 0.5)));
    m_points2.push_back((Vector(-1.0, 2.5)));
    m_points2.push_back((Vector(0.0, 4.0)));
    m_points2.push_back((Vector(1.0, 2.5)));
    m_points2.push_back((Vector(2.0, 0.5)));
    m_points2.push_back((Vector(3.0, 0.075)));
    m_points2.push_back((Vector(4.0, 0.05)));
    m_points2.push_back((Vector(5.0, 0.0)));
}

void GLBox::initPoints3()
{
    // If list is not empty, delete all points.
    if (!m_points3.empty())
        m_points3.clear();

    //sine curve
    m_points3.push_back(Vector(-6.0, 0.0));
    m_points3.push_back(Vector(-3.0, 0.0));
    m_points3.push_back(Vector(-1.5, -1.0));
    m_points3.push_back(Vector(0.0, 0.0));
    m_points3.push_back(Vector(1.5, 1.0));
    m_points3.push_back(Vector(4.5, -1.0));
    m_points3.push_back(Vector(6.0, 0.0));
}

void GLBox::initPoints4()
{
    // If list is not empty, delete all points.
    if (!m_points4.empty())
        m_points4.clear();

    //jumping problem: 2/n, n, jump by 2 at -0.5, double point
    m_points4.push_back(Vector(-8.0, 1.0/4.0));
    m_points4.push_back(Vector(-6.0, 1.0/3.0));
    m_points4.push_back(Vector(-4.0, 1.0/2.0));
    m_points4.push_back(Vector(-2.0, 1.0/1.0));
    m_points4.push_back(Vector(-0.5, 1.0/0.25));

    m_points4.push_back(Vector(-0.5, 6.0));
    m_points4.push_back(Vector(0.5, 6.5));
    m_points4.push_back(Vector(1.5, 7.0));
    m_points4.push_back(Vector(4.5, 8.5));
    m_points4.push_back(Vector(6.5, 9.5));

}

void GLBox::paintLine(double ax, double ay, double bx, double by, double width, double r, double g, double b)
{
    glDisable(GL_LIGHTING); // Disable lighting because single lines cannot be displayed correctly that way.
    glColor3d(r,g,b); // Set the color
    glLineWidth(width); // Set the line width
    glBegin(GL_LINES); // Enable line drawing
    glVertex2d(ax,ay);
    glVertex2d(bx,by);
    glEnd();
    glEnable(GL_LIGHTING); // Enable lighting after painting.
}

void GLBox::paintPoint(double x, double y, double size, double r, double g, double b)
{
    paintLine(x-size, y, x+size,y, 2.5, r, g, b);
    paintLine(x, y-size, x, y+size, 2.5, r, g, b);
}

std::vector<double> GLBox::lagrangeInterpolation(std::vector<Vector> points){
    m_draw = points;

    /* Your code goes here */

    return std::vector<double>;
}

std::vector<Vector> GLBox::evaluateLagrange(std::vector<double> interpolant, double xRangeStart, double xRangeEnd, uint numberPoints){

    /* Your code goes here */

    return std::vector<Vector>;
}

void GLBox::paintPolynomial(std::vector<Vector> points, double width, double r, double g, double b)
{
    unsigned int n = points.size();

    for (unsigned int i = 0; i < n-1; i++)
    {
        Vector p1 = points[i];
        Vector p2 = points[i+1];
        paintLine(p1(0), p1(1), p2(0), p2(1), width, r, g, b);
    }

    paintLine(m_draw[0](0), -10.0, m_draw[0](0), 10.0, width, 0.5, 0.2, 0.1);
    paintLine(m_draw[m_draw.size()-1](0), -10.0, m_draw[m_draw.size()-1](0), 10.0, width, 0.5, 0.2, 0.1);
}

void GLBox::paintGL()
{
    // this method draws the scene into the OpenGL widget
    // usually you do not call this method directly, instead call updateGL(), which in turn calls paintGL()

    // clear the screen and the z-buffer
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    // set the projection matrix and the view transformation
    double ratio = width() / (double) height();

    // define the projection matrix
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity();
    // clipping planes:
    // the whole scene lives within the interval [-10,10]^3
    // use "glOrtho" for parallel projection
    // use "glFrustum" for perspective projection
    glOrtho( -10.0 * ratio * scale,
              10.0 * ratio * scale,
             -10.0 * scale,
              10.0 * scale,
             -50.0 * scale,
              50.0 * scale);

    // mapping of the viewing frustrum onto the viewport
    glViewport( 0, 0, width(), height() );

    // set camera perspective
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    // insert current view transformation
    glMultMatrixd( ViewMatrix);

    // rendering starts here!
    // for every object in the object array, call its "render" method
    //for(unsigned int i = 0; i < objects.size(); i++)
    //{
    //    objects[i]->render();
    //}

    /*
    //paint x^2
    for(int i=-100;i<100;i++)
        paintLine(i/10.0,(i/10.0)*(i/10.0),((i+1)/10.0),((i+1)/10.0)*((i+1)/10.0),2.5, 0.0, 0.0, 1.0);
    //paint house
    paintLine(-2,-4,-2,-2,2.5, 0.0, 1.0, 0.0);
    paintLine(-2,-2,0,-1,2.5, 0.0, 1.0, 0.0);
    paintLine(0,-1,2,-2,2.5, 0.0, 1.0, 0.0);
    paintLine(2,-2,-2,-2,2.5, 0.0, 1.0, 0.0);
    paintLine(-2,-2,2,-4,2.5, 0.0, 1.0, 0.0);
    paintLine(2,-4,2,-2,2.5, 0.0, 1.0, 0.0);
    paintLine(2,-2,-2,-4,2.5, 0.0, 1.0, 0.0);
    paintLine(-2,-4,2,-4,2.5, 0.0, 1.0, 0.0);
    */


    for (unsigned int i = 0; i < m_draw.size(); i++)
    {
        Vector p = m_draw[i];
        paintPoint(p(0), p(1), 0.2, 0.0, 1.0, 0.0);
    }


    paintPolynomial(m_polynomial, 2.5, 0.0, 0.0, 1.0);

    /*
    std::vector<double> coefficients = leastSquaresFit(m_points1, 2);
    std::vector<Vector> polynomial = evaluate(coefficients, -9.0, 9.0, 100);
    paintPolynomial(polynomial, 2.5, 0.0, 0.0, 0.0);
    */


    // perform all output operations
    glFlush();
}

void GLBox::mousePressEvent( QMouseEvent *e )
{
    // a mouse button was pressed

    // if we are in selection mode...
    if (pickingMode)
    {
        // only if it was the left mouse button that was pressed
        if (e->button() == Qt::LeftButton)
        {
            // find the object at the cursor position
            //RenderObject *selectedObject = findObject(e->x(), e->y());
            //emit objectSelected(selectedObject);
        }
    }
    else
    {
        // if we are in moving mode, store the current mouse position (in viewport coordinates)
        // for calculating the cursor movement later (see mouseMoveEvent)
        x0 = e->x();
        y0 = e->y();
    }
}

void GLBox::mouseMoveEvent( QMouseEvent *e )
{
    return;
    // mouse was moved while a mouse button was pressed (dragging)

    // if we are in selection mode, mouse moves don't have any meaning, so do nothing
    if (pickingMode)
        return;

    // so we are in moving mode
    // get the current cursor position (viewport coordinates)
    int x = e->x();
    int y = e->y();

    // calculate the difference to the last cursor position, relative to the dimension of the viewport
    double dx = (x - x0) / height();
    double dy = (y - y0) / height();
    // calculate the difference to the center of the viewport, relative to the dimension of the viewport
    double rx = (x - .5 * width()) / height();
    double ry = (y - .5 * height()) / height();
    // store the cursor position for the next move event
    x0 = x;
    y0 = y;

    int state = e->buttons ();
    // check for left mouse button => rotation
    if ((state & Qt::LeftButton) != 0)
    {
        // update ViewMatrix (using OpenGL implementation)
        glMatrixMode (GL_MODELVIEW);
        glLoadIdentity ();
        double dist = sqrt (dx*dx + dy*dy);
        glRotated (360 * dist, dy, dx, 4 * (ry*dx - rx*dy));
        glMultMatrixd (ViewMatrix);
        glGetDoublev (GL_MODELVIEW_MATRIX, ViewMatrix);
    }

    // check for right mouse button => translation
    if ((state & Qt::RightButton) != 0)
    {
        // Translation
        glMatrixMode( GL_MODELVIEW);
        glLoadIdentity();
        glTranslated( dx * 20.0 * scale, -dy * 20.0 * scale, 0);
        glMultMatrixd( ViewMatrix);
        glGetDoublev( GL_MODELVIEW_MATRIX, ViewMatrix);
    }

    // check for middle mouse button => scaling
    if ((state & Qt::MidButton) != 0)
    {
        // moving the mouse up and down with middle mouse button pressed zooms in and out
        // if the cursor is in the center (x-direction) of the viewport, the zooming is very fast,
        // if the cursor is near the left or right border of the viewport, the zooming is slow and exact
        scale *= exp( -20 * dy * exp( -20 * rx*rx));
    }

    // repaint the scene
    updateGL();
}

void GLBox::mouseReleaseEvent( QMouseEvent * )
{
    // nothing to be done here
}

void GLBox::wheelEvent (QWheelEvent *e)
{
    return;
    // turning the mouse wheel will change the scale and therefore zoom in and out
    double dist = e->delta() / 120.0;  // one wheel "tick" counts for 120
    scale *= exp (dist * log (1.05));
    updateGL();
}

void GLBox::setMovingMode()
{
    pickingMode = false;
}

void GLBox::setPickingMode()
{
    pickingMode = true;
}

/*
RenderObject* GLBox::findObject (int x, int y)
{
    // this is the hit buffer that stores the list of objects beneath the pixel (x,y)
    const int SELECTION_BUFFER_SIZE = 1024;
    GLuint selectionBuffer [SELECTION_BUFFER_SIZE];

    // first set up everything to enable picking in OpenGL
    GLint viewport[4];
    float ratio;

    // the selection buffer is used to store all objects
    // that lie underneath the pixel (x,y)
    glSelectBuffer(SELECTION_BUFFER_SIZE, selectionBuffer);

    // get the viewport
    glGetIntegerv(GL_VIEWPORT, viewport);

    // switch to picking (= selection mode)
    glRenderMode(GL_SELECT);

    // initialize the object ID stack, and add a first dummy element
    glInitNames();
    glPushName (0);

    // set up the current view transformation / projection
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    gluPickMatrix(x,viewport[3]-y,3,3,viewport);
    ratio = (viewport[2]+0.0) / viewport[3];

    glOrtho( -10.0 * ratio * scale,
              10.0 * ratio * scale,
             -10.0 * scale,
              10.0 * scale,
             -50.0 * scale,
              50.0 * scale);

    glMatrixMode(GL_MODELVIEW);

    // now draw all objects
    // every draw operation that collides with the pixel is stored in the selection buffer
    for(unsigned int i = 0; i < objects.size(); i++) 
    {
        glLoadName(i); // set the object's index, so that we can find out which object was hit
        objects[i]->render();
    }
    glFlush();

    // restore everything
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glFlush();

    // get the number of hits
    GLint hits = glRenderMode(GL_RENDER);
    if (hits == 0)
    {
        // nothing was hit, so return the NULL pointer
        return 0L;
    }

    GLuint topIndex = 0;  // ID of the object with the lowest z-value
    GLuint minZ = 0xffffffff;  // currently lowest z-value, initialized to the maximum possible value

    // pointer to traverse the list
    GLuint *ptr = selectionBuffer;
    // go through all entries of the selection buffer
    for (GLint i = 0; i < hits; i++)
    {
        GLuint length = *ptr;  // get the length of the entry
        GLuint z = *(ptr + 1); // get the z-value of the object
        if (z < minZ)   // compare the z-value to the currenty lowest z-value
        {
            // if on top, store the current object ID, and its z-value
            topIndex = *(ptr + 3);
            minZ = z;
        }
        ptr += length+3;
    }
    // the topmost object ID is stored in topIndex, so return the object
    return objects [topIndex];
}
*/
